package org.opens.db;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.lang.Assert;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.opens.db.constant.DbType;
import org.opens.exception.BusinessException;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 数据库连接工具类
 */
public class DynamicDataSourceUtil {

    /**
     * 从数据库url中提取数据库名的正则
     */
    private static final Pattern pattern = Pattern.compile("//.*?/(.*?)\\?");

    /**
     * 从数据库url中提取数据库类型的正则
     */
    private static final Pattern databaseTypePattern = Pattern.compile("jdbc:(.*?):");

    private static Log log = LogFactory.get(DynamicDataSourceUtil.class);

    /**
     * 默认的Connection对象超时时间
     */
    private static final int DEFAULT_CONNECTION_TIMEOUT = 500;

    /**
     * 用于缓存Connection对象
     */
    private static TimedCache<DbType, Connection> CONNECTION_CACHE = new TimedCache<>(DEFAULT_CONNECTION_TIMEOUT);

    private static Map<DbType, Boolean> initializedDriveMap = new HashMap<>();

    /**
     * 用于缓存连接对象
     */
    private static ThreadLocal<Connection> CONNECTION_LOCAL = new ThreadLocal<>();

    private static void mysqlInit() throws ClassNotFoundException {
        try {
            // 有限加载8.0版本的驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            log.info("已初始化数据库驱动: " + "com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // 次优先加载5.x版本的驱动
            Class.forName("com.mysql.jdbc.Driver");
            log.info("已初始化: " + "com.mysql.jdbc.Driver");
        }
    }

    private static void oracleInit() throws ClassNotFoundException {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        log.info("已初始化数据库驱动: " + "oracle.jdbc.driver.OracleDriver");
    }

    private static void sqlServerInit() throws ClassNotFoundException {
        Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        log.info("已初始化数据库驱动: " + "com.microsoft.sqlserver.jdbc.SQLServerDriver");
    }

    private static void kingBase8Init() throws ClassNotFoundException {
        Class.forName("com.kingbase8.Driver");
        log.info("已初始化数据库驱动: " + "com.kingbase8.Driver");
    }

    /**
     * 创建连接对象
     * @param url 连接url
     * @param userName 用户名
     * @param password 密码
     * @return 数据库连接对象
     */
    public static Connection getConnection(String url, String userName, String password) {
        DbType dbType = extractDatabaseTypeFromURL(url);
        Assert.notNull(dbType, "未从url中提取到有效的数据库类型, 终止初始化数据库驱动!");

        Connection localCacheConnection = CONNECTION_LOCAL.get();
        try {
            // 只有未失活的connection对象才会被返回, 否则需要新建
            if (localCacheConnection != null && !localCacheConnection.isClosed()) {
                log.info("ThreadLocal cached connection is used!");
                return localCacheConnection;
            }
        } catch (SQLException e) {
            // pass
            log.warn("ThreadLocal cached connection is closed!");
        }

        Connection connection = null;

        try {
            // 从缓存获取是否已经初始化过了对应的DriverClass, 如果已经初始化过则跳过初始化, 否则进行初始化.
            Boolean initializedFlag = initializedDriveMap.get(dbType);
            if (initializedFlag == null || !initializedFlag) {
                if (DbType.MYSQL.equals(dbType)) {
                    mysqlInit();
                }

                if (DbType.ORACLE.equals(dbType)) {
                    oracleInit();
                }

                if (DbType.SQL_SERVER.equals(dbType)) {
                    sqlServerInit();
                }

                if (DbType.KING_BASE8.equals(dbType)) {
                    kingBase8Init();
                }

                // 进入这里的代码逻辑可能切换了数据库类型, 所以需要清空这个驱动缓存库, 因为切换了数据库后, 上一次的驱动加载缓存还是会保留
                // 暂未验证是否可以同时加载mysql和oracle的驱动, 所以在设置前进行清空
                initializedDriveMap.clear();
                initializedDriveMap.put(dbType, true);
            }

            connection = DriverManager.getConnection(url, userName, password);
            // 设置缓存
            CONNECTION_LOCAL.set(connection);
            log.info("ThreadLocal cached connection is created and cached!");
        } catch (SQLException e) {
            // 获取连接异常
            throw new BusinessException(e);
        } catch (ClassNotFoundException e) {
            // 没有mysql的驱动异常
            throw new BusinessException(e);
        }

        return connection;
    }

    /**
     * 关闭连接对象
     * @param connection 待关闭的连接对象爱能
     */
    public static void close(Connection connection) {
        try {
            if (connection != null && !connection.isClosed()) {
                try {
                    connection.close();
                    log.info("close database connection success!");
                } catch (SQLException e) {
                    // 不做处理
                }
            }
        } catch (SQLException e) {
            // pass
            log.warn("close database connection fail, cause by {}!", e.getMessage());
        }

        // 清除缓存
        CONNECTION_LOCAL.remove();
        log.info("ThreadLocal cached connection remove success!");
    }

    /**
     * 关闭ThreadLocal中的连接对象
     */
    public static void close() {
        close(CONNECTION_LOCAL.get());
    }

    /**
     * 从数据库url中提取数据库名
     * @param url 数据库url
     * @return 数据库名
     */
    public static String extractDatabaseNameFromURL(String url) {
        Matcher matcher = pattern.matcher(url);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }

    /**
     * 从数据库url中提取数据库类型
     * @param url 数据库url
     * @return 数据库类型
     */
    public static DbType extractDatabaseTypeFromURL(String url) {
        Matcher matcher = databaseTypePattern.matcher(url);
        if (matcher.find()) {
            return DbType.getInstance(matcher.group(1));
        }
        return null;
    }

}
